import java.util.Iterator;

import fake.ENextException;
import fake.EReturnException;
import fake.EStopException;

import operators.Equal;
import operators.GreatEq;
import operators.Greater;
import operators.IsIn;
import operators.Less;
import operators.LessEq;
import operators.NotComp;
import operators.NotEq;
import operators.NotIn;

import runtime.ControlStack;
import scope.ScopeStack;
import symbols.HLSet;
import symbols.ISymbol;
import symbols.ISymbolTable;
import symbols.ParameterList;
import symbols.SCallableParameter;
import symbols.SLeftValue;
import symbols.SCallable;
import symbols.Type;

public class Eval implements ParserVisitor {

	private ISymbolTable symbolTableInUse = null;	// must receive the symbol table from the parser
	private ScopeStack scopeStack;	// new a scope stack to track symbols
	private ControlStack controlStack = new ControlStack();

	/**
	 * Overloaded (strings, sets and integers)
	 * @throws Exception 
	 */
	public Object visit(ASTplus node, Object data) throws Exception {
		
		if(data instanceof Integer) {
			Integer op1 = (Integer) data;
			//Integer op2 = (Integer) node.jjtGetChild(0).jjtAccept(this, null);
			Object op2 = node.jjtGetChild(0).jjtAccept(this, null);
			if(op2 instanceof SLeftValue) {
				SLeftValue lValue = (SLeftValue)op2;
				if(! lValue.isSet())
					throw new EUnsetIdentifierException();
				
				Object value = lValue.getValue();
				if(value instanceof Integer)
					if(((Integer)value).intValue() > Integer.MAX_VALUE - op1.intValue())
						throw new ENumberOverflowException();
					else
						return new Integer(op1.intValue() + ((Integer)value).intValue());
				else
					throw new EInvalidTypeException();
				
			}else if(op2 instanceof Integer)
				if(((Integer)op2).intValue() > Integer.MAX_VALUE - op1.intValue())
					throw new ENumberOverflowException();
				else
					return new Integer(op1.intValue() + ((Integer)op2).intValue());
				
			else
				throw new EInvalidTypeException();
			
			
		}else if(data instanceof HLSet) {
			HLSet s1 = (HLSet)data;
			//HLSet s2 = (HLSet) node.jjtGetChild(0).jjtAccept(this, null);
			Object op2 = node.jjtGetChild(0).jjtAccept(this, null);
			HLSet result =  new HLSet();
			result.addAll(s1);
			
			if(op2 instanceof SLeftValue) {
				SLeftValue lValue = (SLeftValue)op2;
				if(! lValue.isSet())
					throw new EUnsetIdentifierException();
				
				Object value = lValue.getValue();
				if(value instanceof HLSet) 
					result.addAll((HLSet)value);
				else
					throw new EInvalidTypeException();
			
			}else if(op2 instanceof HLSet)
				result.addAll((HLSet)op2);
			
			else
				throw new EInvalidTypeException();
			
			return result;
			
		}else if(data instanceof String) {
			StringBuffer result = new StringBuffer( "\"" + strip((String)data));
			Object op = node.jjtGetChild(0).jjtAccept(this, null);	

			if(op instanceof SLeftValue) {
				SLeftValue lValue = (SLeftValue)op;
				if(! lValue.isSet())
					throw new EUnsetIdentifierException();
				
				Object value = lValue.getValue();
				if(value instanceof String || value instanceof Integer || value instanceof HLSet) 
					return result.append(strip("" + op)).append("\"").toString();
				
				else
					throw new EInvalidTypeException();
			
			}else if(op instanceof String || op instanceof Integer || op instanceof HLSet)
				return result.append(strip("" + op)).append("\"").toString();	
			
			else
				throw new EInvalidTypeException();
			
		}else
			throw new EInvalidTypeException();	// weird ! (data must come already evaluated here)
	}
	
	/**
	 * Removed quotation marks at the start and at the end
	 * if one is found at the start of the string.
	 * 
	 * @param s
	 * @return
	 */
	private String strip(String s) {
		String result;
		if(s.charAt(0) == '"')
			result = s.substring(1, s.length()-1);
		else
			result = new String(s);
		return result;
	}

	public Object visit(ASTsum node, Object data) throws Exception {
		Object result = null;
		for (int i = 0; i < node.jjtGetNumChildren(); i++) {
			Object value = node.jjtGetChild(i).jjtAccept(this, result);
			//System.err.println("got value " + value + " of class " + value.getClass());
			if(value instanceof SLeftValue) {
				SLeftValue lValue = (SLeftValue)value;
				if(! lValue.isSet())
					throw new EUnsetIdentifierException();
				
				result = lValue.getValue();
				
			}else
				result = value;
		}			
		return result;
	}

	public Object visit(ASTNumber node, Object data) throws ENumberOverflowException {
		return node.token.getValue();
	}

	/**
	 * Overloaded (strings, sets, integers)
	 * @throws Exception 
	 */
	public Object visit(ASTminus node, Object data) throws Exception {
		Object op2 = node.jjtGetChild(0).jjtAccept(this, null);
		
		if(data instanceof Integer) {
			int op1Val = ((Integer) data).intValue();
			if(op2 instanceof SLeftValue) {
				SLeftValue lValue = (SLeftValue)op2;
				if(! lValue.isSet())
					throw new EUnsetIdentifierException();
				
				Object value = lValue.getValue();
				if(value instanceof Integer) {
					int op2Val = ((Integer) value).intValue();
					int result  = (op1Val - op2Val<0)?0:op1Val - op2Val;
					return new Integer(result);
					
				}else
					throw new EInvalidTypeException();
				
			}else if(op2 instanceof Integer) {
				int op2Val = ((Integer) op2).intValue();
				int result  = (op1Val - op2Val<0)?0:op1Val - op2Val;
				return new Integer(result);
				
			}else
				throw new EInvalidTypeException();				
			
			
		}else if(data instanceof HLSet) {
			HLSet s1 = (HLSet) data;
			HLSet result =  new HLSet();
			result.addAll(s1);
			
			if(op2 instanceof SLeftValue) {
				SLeftValue lValue = (SLeftValue)op2;
				if(! lValue.isSet())
					throw new EUnsetIdentifierException();
				
				Object value = lValue.getValue();
				if(value instanceof HLSet) {
					HLSet s2 = (HLSet)value;
					Iterator it = s2.iterator();
					while(it.hasNext()) 
						result.remove(it.next());
				}else
					throw new EInvalidTypeException();
			
			}else if(op2 instanceof HLSet) {
				HLSet s2 = (HLSet)op2;
				Iterator it = s2.iterator();
				while(it.hasNext()) 
					result.remove(it.next());
			}else
				throw new EInvalidTypeException();
			
			return result;
			
		}else if(data instanceof String) {
			String string = (String) data;			

			if(op2 instanceof SLeftValue) {
				SLeftValue lValue = (SLeftValue)op2;
				if(! lValue.isSet())
					throw new EUnsetIdentifierException();
				
				Object value = lValue.getValue();
				if(value instanceof String) { 
					return string.replaceFirst(strip((String) value), "");
					
				}else if(value instanceof Integer) {
					String sCore = strip(string);
					for(int i=0; i<((Integer)value).intValue() ; i++)
						sCore = sCore.replaceFirst(".", "");
					return "\"" + sCore + "\"";
					
				}else
					throw new EInvalidTypeException();
			
			}else if(op2 instanceof String)
				return string.replaceFirst(strip((String) op2), "");
				
			else if(op2 instanceof Integer) {
				String sCore = strip(string);
				for(int i=0; i<((Integer)op2).intValue() ; i++)
					sCore = sCore.replaceFirst(".", "");
				return "\"" + sCore + "\"";
				
			}else
				throw new EInvalidTypeException();
			
		}else
			throw new EInvalidTypeException();
	}

	/**
	 * Overloaded (strings, sets, integers)
	 * @throws Exception 
	 */
	public Object visit(ASTtimes node, Object data) throws Exception {
		Object op2 = node.jjtGetChild(0).jjtAccept(this, null);
		
		if(data instanceof Integer) {
			Integer op1 = (Integer) data;
			
			if(op2 instanceof SLeftValue) {
				SLeftValue lValue = (SLeftValue)op2;
				if(! lValue.isSet())
					throw new EUnsetIdentifierException();
				
				Object value = lValue.getValue();
				if(value instanceof Integer) {
					long val1 = Math.min(op1.intValue(), ((Integer)value).intValue());
					long val2 = Math.max(op1.intValue(), ((Integer)value).intValue());
					long res = val1 * val2;
					if(res == 0)
						return new Integer(0);
					
					long higherBits = res >> 31;
					if( higherBits == 0)
						return new Integer((int)res);
					else
						throw new ENumberOverflowException();
				
				}else if(value instanceof String) {
					int times = op1.intValue();
					if(times == 0)	return "\"\"";
					String sCore = strip((String)value);
					StringBuffer sb = new StringBuffer("\"");
					for(int i=0; i<times; i++)
						sb.append(sCore);
					return sb.append("\"").toString();
					
				}else
					throw new EInvalidTypeException();
			
			}else if(op2 instanceof Integer) {
				long val1 = Math.min(op1.intValue(), ((Integer)op2).intValue());
				long val2 = Math.max(op1.intValue(), ((Integer)op2).intValue());
				long res = val1 * val2;
				if(res == 0)
					return new Integer(0);
				
				long higherBits = res >> 31;
				if( higherBits == 0)
					return new Integer((int)res);
				else
					throw new ENumberOverflowException();
			
			}else if(op2 instanceof String) {
				int times = op1.intValue();
				if(times == 0)	return "\"\"";
				String sCore = strip((String)op2);
				StringBuffer sb = new StringBuffer("\"");
				for(int i=0; i<times; i++)
					sb.append(sCore);
				return sb.append("\"").toString();
				
			}else
				throw new EInvalidTypeException();
			
		}else if(data instanceof HLSet) {
			HLSet s1 = (HLSet) data;
			//HLSet s2 = (HLSet) op2;
			HLSet result = new HLSet();
			Iterator it1 = s1.iterator();
			
			if(op2 instanceof SLeftValue) {
				SLeftValue lValue = (SLeftValue)op2;
				if(! lValue.isSet())
					throw new EUnsetIdentifierException();
				
				Object value = lValue.getValue();
				if(value instanceof HLSet) {
					HLSet s2 = (HLSet) value;
					while(it1.hasNext()) {
						Object o = it1.next();
						if(s1.contains(o) && s2.contains(o))
							result.add(o);				
					}
					Iterator it2 = s2.iterator();
					while(it2.hasNext()) {
						Object o = it2.next();
						if(s1.contains(o) && s2.contains(o))
							result.add(o);
					}
				}else
					throw new EInvalidTypeException();
			
			}else if(op2 instanceof HLSet) {
				HLSet s2 = (HLSet) op2;
				while(it1.hasNext()) {
					Object o = it1.next();
					if(s1.contains(o) && s2.contains(o))
						result.add(o);				
				}
				Iterator it2 = s2.iterator();
				while(it2.hasNext()) {
					Object o = it2.next();
					if(s1.contains(o) && s2.contains(o))
						result.add(o);
				}
			
			}else
				throw new EInvalidTypeException();
			
			return result;
			
		}else if(data instanceof String) {
			int times;
			
			if(op2 instanceof Integer) {
				times = ((Integer)op2).intValue();
			
			}else if(op2 instanceof SLeftValue) {
				SLeftValue lValue = (SLeftValue)op2;
				if(! lValue.isSet())
					throw new EUnsetIdentifierException();

				Object value = lValue.getValue();
				if(value instanceof Integer) 
					times = ((Integer)value).intValue();
				else
					throw new EInvalidTypeException();
				
			}else 
				throw new EInvalidTypeException();	
			
			// proceed with operation
			if(times == 0) return "\"\"";
			String sCore = strip((String)data);
			StringBuffer sb = new StringBuffer("\"");
			for(int i=0; i<times; i++)
				sb.append(sCore);
			return sb.append("\"").toString();
		
		}else
			throw new EInvalidTypeException();
	}

	public Object visit(ASTdiv node, Object data) throws Exception {
		Object op2 = node.jjtGetChild(0).jjtAccept(this, null);
		
		if(!(data instanceof Integer))
			throw new EInvalidTypeException();
		
		Integer op1 = (Integer)data;
		
		if(op2 instanceof SLeftValue) {
			SLeftValue lValue = (SLeftValue)op2;
			if(! lValue.isSet())
				throw new EUnsetIdentifierException();
			
			Object value = lValue.getValue();
			if(value instanceof Integer) {
				int intValue = ((Integer)value).intValue();
				if(intValue == 0)
					throw new EDivisionByZeroException();
				else
					return new Integer(op1.intValue() / intValue);

			}else
				throw new EInvalidTypeException();
			
		}else if(op2 instanceof Integer) {
			int op2Value = ((Integer)op2).intValue();
			if(op2Value == 0)
				throw new EDivisionByZeroException();
			else
				return new Integer(op1.intValue() / op2Value);
		
		}else 
			throw new EInvalidTypeException();
	}

	public Object visit(ASTmod node, Object data) throws Exception {
		//int op2Val = ((Integer)node.jjtGetChild(0).jjtAccept(this, null)).intValue();
		Object op2 = node.jjtGetChild(0).jjtAccept(this, null);

		if(op2 instanceof SLeftValue) {
			SLeftValue lValue = (SLeftValue)op2;
			if(! lValue.isSet())
				throw new EUnsetIdentifierException();
			
			Object value = lValue.getValue();
			if(value instanceof Integer) {
				int op2Val = ((Integer)value).intValue();
				
				if(data instanceof Integer) {		
					if(op2Val == 0)
						throw new EModByZeroException();
					else
						return new Integer(((Integer) data).intValue() % op2Val);
				
				}else if(data instanceof String) { 
					if(((String)data).length() > op2Val)
						return "\"" + strip(((String)data)).substring(0, op2Val) + "\"";
					else
						return data;
				}else
					throw new EInvalidTypeException();
				

			}else
				throw new EInvalidTypeException();
			
		}else if(op2 instanceof Integer) {
			int op2Val = ((Integer)op2).intValue();
			
			if(data instanceof Integer) {		
				if(op2Val == 0)
					throw new EModByZeroException();
				else			
					return new Integer(((Integer) data).intValue() % op2Val);
			
			}else if(data instanceof String) { 
				if(((String)data).length() - 2 > op2Val)	// counting "
					return "\"" + strip(((String)data)).substring(0, op2Val) + "\"";
				else
					return data;
				
			}else
				throw new EInvalidTypeException();
			
		}else
			throw new EInvalidTypeException();
	}

	public Object visit(SimpleNode node, Object data) {
		// TODO Auto-generated method stub
		return null;
	}

	public Object visit(ASTErrorReplace node, Object data) {
		// TODO Auto-generated method stub
		return null;
	}

	public Object visit(ASTErrorMissing node, Object data) {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * With a body:
	 * while, repeat, for, fn, predicate and sub
	 * 
	 * @param data is the ICallable entry of the symbol table representing the fn/struct called
	 */
	public Object visit(ASTbody node, Object data) throws Exception {
		SCallable called = (SCallable)data;
		//System.out.println("In ASTbody!");
		//System.out.println("fn = " + called);
		//symbolTableInUse.print();
		
		// eval body statements one by one ; capture return stmt and its value
		try{
			for(int i=0; i<node.jjtGetNumChildren(); i++) 
				node.children[i].jjtAccept(this, called);
			
		}catch(EReturnException e) {
			if(called.is(Type.SUB))
				if(e.getValue() != null)
					throw new ESubReturnValueException();
				else
					return null;	// sub return no values
			
			else if(called.is(Type.FUNCTION)) {
				Object val = e.getValue();
				if(val == null	)
					throw new EFunctionMustReturnAValue();
			
				else if(val instanceof Boolean)
					throw new EFunctionCanNotReturnBooleanException();
				
				// checking return type
				else if(val instanceof Integer && !(called.getReturnType() == Type.NUM))
					throw new EFunctionBadReturnTypeException();
				
				else if(val instanceof String && !(called.getReturnType() == Type.STR))
					throw new EFunctionBadReturnTypeException();
				
				else if(val instanceof HLSet && !(called.getReturnType() == Type.SET))
					throw new EFunctionBadReturnTypeException();
						
				else
					return val;
				
			}else if(called.is(Type.PREDICATE))
				if(e.getValue() instanceof Boolean)
					return e.getValue();
				else
					throw new EPredicateMustReturnABooleanException();
			
			else
				return e.getValue();
		}
		return null;
	}

	/**
	 * A clause only evaluates its sub-AST(s)
	 * and returns no value
	 * 
	 * TODO scope
	 * @throws Exception 
	 */
	public Object visit(ASTclause node, Object data) throws Exception {
		int numChildren = node.jjtGetNumChildren();
		Object result = null;
		//System.out.println("Got a clause with " + numChildren + " children");
		
		for(int i=0; i< numChildren; i++) {
			//System.out.println(node.children[i].getClass());
			result = node.children[i].jjtAccept(this, null);
		}
		return result;
	}

	public Object visit(ASTproduct node, Object data) throws Exception {
		int children = node.jjtGetNumChildren();
		Object result = null;
		for (int i = 0; i < children; i++) {
			Object value = node.jjtGetChild(i).jjtAccept(this, result);
			//System.err.println("got value " + value);
			if(value instanceof SLeftValue) {
				SLeftValue lValue = (SLeftValue)value;
				if(! lValue.isSet())
					throw new EUnsetIdentifierException();
				else
					result = lValue.getValue();
			}else
				result = value;
		}			
		return result;
	}

	public Object visit(ASTindexed node, Object data) throws Exception {
		Object firstChild = node.jjtGetChild(0).jjtAccept(this, null);
		Object secondChild = node.jjtGetChild(1).jjtAccept(this, null);
		String string = null;	// op1 must be a string (or of type string)
		
		// dealing with op1
		
		if(firstChild instanceof String) {
			string = (String)firstChild;
			
		}else if(firstChild instanceof SLeftValue) {
			SLeftValue lValue = (SLeftValue)firstChild;
			
			if(! lValue.isSet())
				throw new EUnsetIdentifierException();
			
			Object value = lValue.getValue();
			if(value instanceof String)
				string = (String)value;
			
			else
				throw new EInvalidTypeException();			
			
		}else
			throw new EInvalidTypeException();
		
		// dealing with op2
		
		if(secondChild instanceof Integer) {
			int index = ((Integer) secondChild).intValue();
			if(index == 0 || index > string.length()-2) 
				throw new EOutOfBoundException();
			
			return "\"" + string.charAt(index) + "\"";
			
		}else if(secondChild instanceof String) {
			String substring = (String)secondChild;
			int position = string.indexOf(substring.replaceAll("\"", ""));				
			return new Integer((position == -1)?0:position);
			
		}else if(secondChild instanceof SLeftValue) {
			SLeftValue lValue = (SLeftValue)secondChild;
			
			if(! lValue.isSet())
				throw new EUnsetIdentifierException();
			
			Object value = lValue.getValue();
			if(value instanceof Integer) {
				int index = ((Integer) value).intValue();
				if(index == 0 || index > string.length()-2) 
					throw new EOutOfBoundException();
				
				return "\"" + string.charAt(index) + "\"";				
				
			}else if(value instanceof String) {
				String substring = (String)value;
				int position = string.indexOf(substring.replaceAll("\"", ""));				
				return new Integer((position == -1)?0:position);			
			
			}else 
				throw new EInvalidTypeException();		
			
			
		}else
			throw new EInvalidTypeException();
	}

	public Object visit(ASTsimple_decl node, Object data) {
		// TODO Auto-generated method stub
		return null;
	}

	public Object visit(ASTfn_decl node, Object data) {
		// TODO Auto-generated method stub
		return null;
	}

	public Object visit(ASTsub_decl node, Object data) {
		// TODO Auto-generated method stub
		return null;
	}

	// This node is useless!
	public Object visit(ASTpredicate_decl node, Object data) {
		return null;
	}

	public Object visit(ASTident_list node, Object data) {
		// TODO Auto-generated method stub
		return null;
	}

	public Object visit(ASTparam_decls node, Object data) {
		// TODO Auto-generated method stub
		return null;
	}

	public Object visit(ASTparam_decl node, Object data) {
		// TODO Auto-generated method stub
		return null;
	}

	public Object visit(ASTBYREFERENCE node, Object data) {
		// TODO Auto-generated method stub
		return null;
	}

	public Object visit(ASTBYVALUE node, Object data) {
		// nothing to do
		return null;
	}

	/**
	 * Modifies the symbol table
	 * @return no value (a statement does not return value)
	 */
	public Object visit(ASTassignment node, Object data) throws Exception {
		SLeftValue  lhsSymbol = (SLeftValue)node.jjtGetChild(0).jjtAccept(this, node);	// forward this node type as action marker
		Object  rhs = node.jjtGetChild(1).jjtAccept(this, null);	// value
		if(rhs instanceof SLeftValue)
			rhs = ((SLeftValue)rhs).getValue();
		
		//System.err.println("rhs " + rhs);		
		//System.out.println("lhs = " + lhsSymbol.hashCode() + " " + lhsSymbol.getClass());
		
		if(lhsSymbol == null)
			throw new EIdentifierIsNotALeftValueException();
		
		else {
			if(lhsSymbol.is(Type.NUM))
				if(rhs instanceof Integer) 
					lhsSymbol.setValue(rhs);
				else
					throw new EInvalidTypeException();
				
			else if(lhsSymbol.is(Type.OBJ)) 
				lhsSymbol.setValue(rhs);
			
			else if(lhsSymbol.is(Type.SET))
				if(rhs instanceof HLSet)
					lhsSymbol.setValue(rhs);			
				else
					throw new EInvalidTypeException();
			
			else if(lhsSymbol.is(Type.STR))
					if(rhs instanceof String)
						lhsSymbol.setValue(rhs);
					else
						throw new EInvalidTypeException();
			
			else {
				// 	TODO indexed
				System.err.println("indexed to be done");
			}
		}
		return null; // no value	
	}

	public Object visit(ASTif_stat node, Object data) throws Exception {
		//System.out.println("In ASTif_stat!");
		
		boolean condition	 = ((Boolean)node.children[0].jjtAccept(this, null)).booleanValue();
		
		if(condition)
			node.children[1].jjtAccept(this, null);	// not supposed to return anything
		
		else if(node.jjtGetNumChildren() > 2)
			node.children[2].jjtAccept(this, null);
		
		return null;	// not supposed to return a value (stmt)
	}

	public Object visit(ASTfor_explicit node, Object data) throws Exception {
		SLeftValue i 		= (SLeftValue)node.children[0].jjtAccept(this, null);
		Object valueFrom 	= node.children[1].jjtAccept(this, null);
		Object valueTo 		=  node.children[2].jjtAccept(this, null);
		Object valueStep	= node.children[3].jjtAccept(this, null);
		ASTbody body 		= (ASTbody)node.children[4];
		int from, to, step;
			
		if(! i.is(Type.NUM))
			throw new ELoopBadIdentifierTypeException();
		
		if(valueFrom instanceof Integer)		
			from = ((Integer)valueFrom).intValue();
		else
			throw new ELoopValueFromIsNotANumber();
		
		if(valueTo instanceof Integer)
			to =  ((Integer)valueTo).intValue();
		else
			throw new ELoopValueToMustBeANumberException();
		
		if(valueStep instanceof Integer)
			step =  ((Integer)valueStep).intValue();
		else
			throw new ELoopValueStepMustBeANumberException();
		
		try {
			i.setValue(new Integer(from));
			int index = from;
			if(step > 0) // counting up
				while(index <= to) {
					try{
						body.jjtAccept(this, null);
						
					}catch(ENextException ne) {
						String idNexted = (String)ne.getValue();
						if(idNexted.equals(i.getName())) {						
							index += step;
							i.setValue(new Integer(index));
							continue;
						}
						else
							throw ne; // throw forward (for outer loops)						
					}						
					index += step;
					i.setValue(new Integer(index));
				}
			else		// counting down
				while(index > to) {
					try {
						body.jjtAccept(this, null);
						
					}catch(ENextException ne) {
						String idNexted = (String)ne.getValue();
						if(idNexted.equals(i.getName())) {
							index -= step;
							i.setValue(new Integer(index));
							continue;
						}
						else
							throw ne; // throw forward (for outer loops)						
					}						
					index -= step;
					i.setValue(new Integer(index));			
				}
			
		}catch(EStopException se) {
			String idNexted = (String)se.getValue();
			if(idNexted.equals(i.getName()))
				return null; // done here
			else
				throw se; // throw forward (for outer loops)			
		}
		
		return null;
	}

	public Object visit(ASTfor_in node, Object data) throws Exception {
		SLeftValue var 	= (SLeftValue)node.children[0].jjtAccept(this, null);
		Object o 		= node.children[1].jjtAccept(this, null);
		ASTbody body 	= (ASTbody)node.children[2];
		
		try{
			if(o instanceof String) {
				if(! var.is(Type.STR))
					throw new ELoopImplicitIteratorMustBeOfTypeSTRException();
				String string = strip((String)o);
				char[] chars = string.toCharArray();
				for(int i = 0; i<chars.length; i++) {
					var.setValue("" + chars[i]);
					try{
						body.jjtAccept(this, null);
						
					}catch(ENextException ne) {
						String idNexted = (String)ne.getValue();
						if(idNexted.equals(var.getName())) 
							continue;
						else
							throw ne; // throw forward (for outer loops)						
					}						
				}
			}else if(o instanceof HLSet) {
				if(! var.is(Type.OBJ))
					throw new ELoopImplicitIteratorMustBeOfTypeOBJException();
				
				HLSet set = (HLSet)o;
				Iterator it = set.iterator();
				while(it.hasNext()) {
					var.setValue(it.next());
					try {
						body.jjtAccept(this, null);
					}catch(ENextException ne) {
						String idNexted = (String)ne.getValue();
						if(idNexted.equals(var.getName()))
							continue;
						else
							throw ne; // throw forward (for outer loops)
					}	
				}
			}else
				throw new ELoopImplicitDoesOnlyStrOrSetException();
			
		}catch(EStopException se) {
			String idNexted = (String)se.getValue();
			if(idNexted.equals(var.getName()))
				return null; // done here
			else
				throw se; // throw forward (for outer loops)			
		}
		
		return null;
	}

	public Object visit(ASTwhile_stat node, Object data) throws Exception {
		//boolean condition =((Boolean)node.children[0].jjtAccept(this, data)).booleanValue();
		
		while(((Boolean)node.children[0].jjtAccept(this, data)).booleanValue()) {
			node.children[1].jjtAccept(this, data);
		}
		return null;
	}

	public Object visit(ASTrepeat_stat node, Object data) throws Exception {
		do{
			node.children[0].jjtAccept(this, data);
		}while(! ((Boolean)node.children[1].jjtAccept(this, data)).booleanValue());
		
		return null;
	}

	public Object visit(ASTfn_call node, Object data) throws Exception {
		Object result;
		SCallable fn = (SCallable)node.children[0].jjtAccept(this, null);
		ASTparameters paramNode = (ASTparameters)node.children[1];
		
		// setting up symbol table
		//bad: symbolTableInUse = fn.cloneSymbolTable();
		ISymbolTable newSt = fn.cloneSymbolTable();
		// uncomment below to activate dynamic scoping
		newSt.setParent(symbolTableInUse); 
		// active static scoping TODO complete this
		
		
		ISymbolTable parent = controlStack.getSymbolTable(newSt.getParent());
		if(parent != null) {
			System.out.println("parent: " );
			parent.print();
		}
		//newSt.setParent(parent);
		
		scopeStack.push(newSt);	// pushing on top of stack but still using
								// symbolTableInUse until parameters are 
								// all computed
		
		//System.out.println("before param : "  + symbolTableInUse);
		
		// dealing with parameters
		ParameterList paramList = fn.getParameterList();
		//System.out.println("For " + fn.getName() + " got this param list : " + paramList);
		
		// fill the symbol table in use with value for parameters in paramList
		paramNode.jjtAccept(this, paramList);	
		//StackFrame currentStackFrame = new StackFrame(paramList, symbolTableInUse);
		//callStack.push(currentStackFrame);
		
		//System.out.println("after param : "  + symbolTableInUse);
		symbolTableInUse = newSt;	// now going into fn scope
		
		/*
		if(fn.getName().equals("fact2")) {
			newSt.print();
			Object result2 = newSt.get("result");
			System.out.println("from fact2 , result=" + result2 + " in st=" + newSt.hashCode());
		}
		*/
		
		//controlStack.push(new ActivationRecord(symbolTableInUse));
				
		result = ((ASTbody)fn.getBody()).jjtAccept(this, fn);
		//System.out.println("result: " + result);
		
		//controlStack.pop();
		
		scopeStack.pop();
		symbolTableInUse = (ISymbolTable)scopeStack.peek();
		//System.out.println("poped and now using " + symbolTableInUse.hashCode()
		//		+ " with n=" + ((ISymbol)symbolTableInUse.get("n")));
		return result;
	}

	/**
	 * Fill the symbol table in use with value for parameters in paramList
	 * @throws Exception 
	 */
	public Object visit(ASTparameters node, Object data) throws Exception {
		ParameterList paramList = (ParameterList)data;
		Object value;
		ISymbolTable newSt = (ISymbolTable)scopeStack.peek(); // contains a cloned st from fn entry def
		
		if(paramList.size() > node.jjtGetNumChildren())
			throw new EMissingParametersException();
		
		else if(paramList.size() < node.jjtGetNumChildren())
			throw new ETooManyParametersException();
		
		for(int i = 0; i < node.jjtGetNumChildren(); i++) {
			Object o = node.children[i].jjtAccept(this, null);
			//System.out.println("o=" + o);
			if(o instanceof SLeftValue)
				value = ((SLeftValue)o).getValue();
			else 
				value = o;
			
			SCallableParameter p = (SCallableParameter)paramList.get(i);
			if(p.isReference()) {
				//System.out.println("We have a reference! ");
				SLeftValue v = (SLeftValue) symbolTableInUse.get(((SLeftValue)o).getName());
				newSt.put(p.getName(), v);
				
			}else{
				if(!p.is(Type.OBJ) && (
							value instanceof Integer && !p.is(Type.NUM)
						|| value instanceof String && !p.is(Type.STR)
						|| value instanceof HLSet && !p.is(Type.SET)))
					throw new EWrongParameterTypeException();
				
				p.setValue(value); // useless	; symbol table much change:			
				((SLeftValue)newSt.get(p.getName())).setValue(value);
			}
			
			//newSt.print();
		}
		return null;
	}

	public Object visit(ASTnext_stat node, Object data) throws Exception {
		throw new ENextException(((SLeftValue)node.children[0].jjtAccept(this, null)).getName());
	}

	public Object visit(ASTstop_stat node, Object data) throws Exception {
		throw new EStopException(((SLeftValue)node.children[0].jjtAccept(this, null)).getName());
	}

	public Object visit(ASTreturn_stat node, Object data) throws Exception {
		//return node.children[0].jjtAccept(this, null);
		throw new EReturnException(node.children[0].jjtAccept(this, null));
	}

	public Object visit(ASTBOOLEANT node, Object data) {
		return new Boolean(true);
	}

	public Object visit(ASTBOOLEANF node, Object data) {
		return new Boolean(false);
	}

	public Object visit(ASTNOTHING node, Object data) {
		return null;	// the value of null in HL
	}

	public Object visit(ASTor node, Object data) throws Exception {
		boolean result = ((Boolean)node.children[0].jjtAccept(this, null)).booleanValue();
		for(int i=1; i<node.jjtGetNumChildren(); i++) 
			result |= ((Boolean)node.children[i].jjtAccept(this, null)).booleanValue();
		return new Boolean(result);
	}

	public Object visit(ASTand node, Object data) throws Exception {
		boolean result = true;
		for(int i=0; i<node.jjtGetNumChildren(); i++) 
			result &= ((Boolean)node.children[i].jjtAccept(this, null)).booleanValue();
		return new Boolean(result);
	}

	public Object visit(ASTisa_comparison node, Object data) throws Exception {
		Object op1 = node.jjtGetChild(0).jjtAccept(this, null);
		Object op2 = node.jjtGetChild(1).jjtAccept(this, null);
		
		int type = ((Integer)op2).intValue();
		
		if(op1 instanceof SLeftValue)
			return new Boolean(((ISymbol)op1).is(type));
		
		// literals
		else if(type == Type.OBJ)
			return new Boolean(true);	// all types are subtypes of OBJ
		else if(op1 instanceof String)
			return new Boolean(type == Type.STR );
		else if(op1 instanceof Integer)
			return new Boolean(type == Type.NUM);
		else if(op1 instanceof HLSet)
			return new Boolean(type == Type.SET);
		else
			return new Boolean(false);	// should never comes here
	}

	/**
	 * Ask for evaluation down the tree, to its unique child.
	 * Returns the opposite boolean value of the resulting one
	 */
	public Object visit(ASTnot node, Object data) throws Exception {
		return new Boolean( ! ((Boolean)node.children[0].jjtAccept(this, null)).booleanValue());
	}

	public Object visit(ASTcomparison node, Object data) throws Exception {
		//System.out.println("in ASTcomparison!");
		
		Object op1 = node.jjtGetChild(0).jjtAccept(this, null);
		Object operator = node.jjtGetChild(1).jjtAccept(this, null);
		Object op2 = node.jjtGetChild(2).jjtAccept(this, null);
		
		// extract value from left-value (identifier) if any
		
		if(op1 instanceof SLeftValue) {
			Object val1 = (op1 instanceof SLeftValue)?((SLeftValue)op1).getValue():op1;
			Object val2 = (op2 instanceof SLeftValue)?((SLeftValue)op2).getValue():op2;
			
			if(((SLeftValue)op1).is(Type.OBJ)) {
				// it can be compared with null as well				
				if(operator instanceof Equal && val2 == null)
						return new Boolean(val1 == null);
				
				else if(operator instanceof NotEq && val2 == null)
						return new Boolean(val1 != null);
			}
			
			// for the rest we only care about the value (regardless of the type or anything else)
			op1 = ((SLeftValue)op1).getValue();
		}
		
		if(op2 instanceof SLeftValue) {
			Object val1 = (op1 instanceof SLeftValue)?((SLeftValue)op1).getValue():op1;
			Object val2 = (op2 instanceof SLeftValue)?((SLeftValue)op2).getValue():op2;
			
			if(((SLeftValue)op2).is(Type.OBJ)) {
				// it can be compared with null as well				
				if(operator instanceof Equal && val1 == null)
						return new Boolean(val2 == null);
				
				else if(operator instanceof NotEq && val1 == null)
						return new Boolean(val2 != null);
			}
			
			// for the rest we only care about the value (regardless of the type or anything else)
			op2 = ((SLeftValue)op2).getValue();
		}
				
		// test comparison between 2 null values (not necessarily identifiers)
		if(op1 ==null && op2 == null)
			if(operator instanceof Equal) 
				return new Boolean(true);
			else if(operator instanceof NotEq)
				return new Boolean(false);
			else
				throw new EInvalidTypeComparisonException();
		else if(op1 == null || op2 == null) // can not compare null with anything but an obj
			//throw new EInvalidTypeComparisonException();
			if(operator instanceof Equal)
				return new Boolean(false);	// not quite conform to the grammar but needed to pass the tests! (email dec 11 12h)\
			else if(operator instanceof NotEq)
				return new Boolean(true);
		
		//System.out.println("operator is an  " + operator.getClass());
		//System.out.println("Op1 et Op2 are of same type");
		
		if(op1 instanceof Integer) {
			int val1 = ((Integer)op1).intValue();
			
			if(op2 instanceof Integer) {
				int val2 = ((Integer)op2).intValue();
				
				//System.out.println(" val1 = " + val1 + " and val2 = " + val2);
				
				if(operator instanceof Equal)				
					return new Boolean(val1 == val2);				
				else if(operator instanceof GreatEq)
					return new Boolean(val1 >= val2);
				else if(operator instanceof Greater)
					return new Boolean(val1 > val2);
				else if(operator instanceof Less)
					return new Boolean(val1 < val2);
				else if(operator instanceof LessEq)
					return new Boolean(val1 <= val2);
				else if(operator instanceof NotEq)
					return new Boolean(val1 != val2);
				else
					throw new EUndefinedOperatorException();
				
			}else if(op2 instanceof HLSet) {
				HLSet val2 = (HLSet)op2;
				
				if(operator instanceof IsIn)
					return new Boolean(val2.contains(op1));
				else if(operator instanceof NotIn)
					return new Boolean(! val2.contains(op1));
				
			}else
				throw new EInvalidTypeException();					
			
		}else if(op1 instanceof String) {
			String val1 = (String)op1;
			
			if(op2 instanceof HLSet) {
				HLSet val2 = (HLSet)op2;
				
				if(operator instanceof IsIn)
					return new Boolean(val2.contains(val1));
				else if(operator instanceof NotIn)
					return new Boolean(! val2.contains(val1));
				
			}else if(op2 instanceof String) {
				String val2 = strip((String)op2);
				val1 = strip(val1);
				
				// same operations as for Integers are valid with String
				if(operator instanceof Equal)				
					return new Boolean(val1.equals(val2));				
				else if(operator instanceof GreatEq)
					return new Boolean(val1.compareTo(val2) >= 0);
				else if(operator instanceof Greater)
					return new Boolean(val1.compareTo(val2) > 0);
				else if(operator instanceof Less)
					return new Boolean(val1.compareTo(val2) < 0);
				else if(operator instanceof LessEq)
					return new Boolean(val1.compareTo(val2) <= 0);
				else if(operator instanceof NotEq)
					return new Boolean(!val1.equals(val2));
				else if(operator instanceof IsIn)
					return new Boolean(val2.matches(".*" + strip(val1) + ".*"));
				else if(operator instanceof NotIn)
					return new Boolean(! val2.matches(".*" + strip(val1) + ".*"));
				else
					throw new EUndefinedOperatorException();
			}else
				throw new EInvalidTypeException();	// only strings and sets are acceptable as second operand here
			
		}else if(op1 instanceof HLSet) {
			HLSet val1 = (HLSet)op1;
			
			if(op2 instanceof HLSet) {
				HLSet val2 = (HLSet)op2;
	
				// same operations as for Integers and String plus NotComp
				if(operator instanceof Equal)				
					return new Boolean(val1.equals(val2));
				else if(operator instanceof GreatEq)
					return new Boolean(val1.greatEqThan(val2));
				else if(operator instanceof Greater)
					return new Boolean(val1.greaterThan(val2));
				else if(operator instanceof Less)
					return new Boolean(val1.lessThan(val2));
				else if(operator instanceof LessEq)
					return new Boolean(val1.lessEqThan(val2));
				else if(operator instanceof NotEq)
					return new Boolean(!val1.equals(val2));
				else if(operator instanceof NotComp)
					return new Boolean(val1.notComp(val2));
				else if(operator instanceof IsIn)
					return new Boolean(val2.contains(val1));
				else if(operator instanceof NotIn)
					return new Boolean(! val2.contains(val1));
				else
					throw new EUndefinedOperatorException();
			}else
				throw new EInvalidTypeException();
			
		}
		/*
		else { // type of op1 is obj ; only valid comparison is with null
			if(operator instanceof Equal)
				if(op2 == null)
					return new Boolean(op1 == op2);
				else
					throw new EUndefinedOperatorException();
			
			else if(operator instanceof NotEq)
				if(op2 == null)
					return new Boolean(op1 != op2);
				else
					throw new EUndefinedOperatorException();
			else
				throw new EUndefinedOperatorException();
		}
		*/
		throw new EInvalidTypeException();
	}

	public Object visit(ASTLESS node, Object data) {
		return Less.getInstance();
	}

	public Object visit(ASTLESSEQ node, Object data) {
		return LessEq.getInstance();
	}

	public Object visit(ASTGREATER node, Object data) {
		return Greater.getInstance();
	}

	public Object visit(ASTGREATEQ node, Object data) {
		return GreatEq.getInstance();
	}

	public Object visit(ASTNOTCOMP node, Object data) {
		return NotComp.getInstance();
	}

	public Object visit(ASTEQUAL node, Object data) {
		return Equal.getInstance();
	}

	public Object visit(ASTNOTEQ node, Object data) {
		return NotEq.getInstance();
	}

	public Object visit(ASTISIN node, Object data) {
		return IsIn.getInstance();
	}

	public Object visit(ASTNOTIN node, Object data) {
		return NotIn.getInstance();
	}

	public Object visit(ASTSetLiteral node, Object data) throws Exception {
		HLSet result = new HLSet();
		int children = node.jjtGetNumChildren();
		for (int i = 0; i < children; i++)
			result.add(node.jjtGetChild(i).jjtAccept(this, null));
		return result;
	}

	public Object visit(ASTSET node, Object data) {
		return new Integer(Type.SET);
	}

	public Object visit(ASTNUM node, Object data) {
		return new Integer(Type.NUM);
	}

	public Object visit(ASTSTR node, Object data) {
		return new Integer(Type.STR);
	}

	public Object visit(ASTOBJ node, Object data) {
		return new Integer(Type.OBJ);
	}

	public Object visit(ASTIdentifier node, Object caller) throws Exception {
		// TODO fix this! : symbol table problem (scope)
		
		//ISymbol sym = ((ISymbolTable)scopeStack.peek()).get((String)node.token.image);
		ISymbol sym = symbolTableInUse.get((String)node.token.image);
		//symbolTableInUse.print();
		//((ISymbolTable)scopeStack.peek()).print();
		//System.out.println("Evaluation ASTidentifier node for " + node.token);
		//System.out.println("Identifier " + sym);
		
		if(caller instanceof ASTassignment) {
			if(sym instanceof SLeftValue) {
				SLeftValue symbol = (SLeftValue)sym;
				return symbol;
				
			}else if(sym instanceof SCallable) {
				//SymbolWtScope symbol = (SymbolWtScope)sym;
				return null;	// no value for an assignment
				
			}else{
				throw new EUnknownTypeException();
			}
			
		}
		
		if(sym != null)
			return sym;
		else
			throw new EUndefinedIdentifierException();
	}

	public Object visit(ASTString node, Object data) throws Exception {
		//System.err.println("ASTString called for " + node.token.getValue());
		//return ((String)node.token.getValue()).replaceFirst("String ", "");
		return node.token.getValue();	// this will never throw an Exception (used for Number)
	}
	
	/**
	 * Sets the environment
	 * To be called once after parsing is done
	 * 
	 * @param st
	 */
	public void setSymbolTable(ISymbolTable st) {
		this.symbolTableInUse = st;
		//st.print();
		scopeStack = new ScopeStack();
		scopeStack.push(st);	// put global scope in
	}

	/* ADDED FOR PART 3 */
	
	public Object visit(ASTfn_call_print node, Object data) throws Exception {
		Object string = node.jjtGetChild(0).jjtAccept(this, null);
		if(string instanceof String)
			System.out.print(strip((String)string));
		else
			System.out.print(strip("" + string));	// to implement new functionality (see email..)
		
		//throw new EInvalidTypeException();
		return null; 	// string;	// useless stmt return no values
	}

	public Object visit(ASTfn_call_println node, Object data) throws Exception {
		Object string = node.jjtGetChild(0).jjtAccept(this, null);
		if(string instanceof String)
			System.out.println(strip((String)string));
		else
			System.out.println("" + string);
		
		//throw new EInvalidTypeException();
		return null;	//string + "\n";	// useless stmt return no values
	}

	public Object visit(ASTfn_call_strlen node, Object data) throws Exception {
		Object string = node.jjtGetChild(0).jjtAccept(this, null);
		if(string instanceof String)
			return new Integer(((String)string).length()-2);
		
		throw new EInvalidTypeException();
	}

	public Object visit(ASTfn_call_setcard node, Object data) throws Exception {
		Object set = node.jjtGetChild(0).jjtAccept(this, null);
		if(set instanceof HLSet)
			return new Integer(((HLSet)set).size());
		
		throw new EInvalidTypeException();
	}

	public Object visit(ASTfn_call_env node, Object data) throws Exception {
		symbolTableInUse.print();
		return null;
	}
}
